<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<script src="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script>
<link href="//cdn.bootcss.com/pace/1.0.2/themes/pink/pace-theme-flash.css" rel="stylesheet">
<style>
    .pace .pace-progress {
        background: #1E92FB; /*进度条颜色*/
        height: 3px;
    }
    .pace .pace-progress-inner {
         box-shadow: 0 0 10px #1E92FB, 0 0 5px     #1E92FB; /*阴影颜色*/
    }
    .pace .pace-activity {
        border-top-color: #1E92FB;    /*上边框颜色*/
        border-left-color: #1E92FB;    /*左边框颜色*/
    }
</style>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">



  <meta name="google-site-verification" content="true">

<script data-ad-client="ca-pub-3331353808689126" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>







  <meta name="baidu-site-verification" content="true">







  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">




  
  
  
  

  
    
    
  

  
    
      
    

    
  

  

  
    
      
    

    
  

  
    
      
    

    
  

  
    
    
    <link href="https://fonts.cat.net/css?family=Lato:300,300italic,400,400italic,700,700italic|Roboto Slab:300,300italic,400,400italic,700,700italic|Lobster Two:300,300italic,400,400italic,700,700italic|PT Mono:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="存储,分布式,原理,">





  <link rel="alternate" href="/atom.xml" title="爱吃jelly的Jelly" type="application/atom+xml">






<meta name="description" content="Ceph浅析：结构、工作原理及流程 其命名和UCSC（Ceph诞生地）的吉祥物有关，这个吉祥物是“Sammy”，一个香蕉色的蛞蝓，就是头足类中无壳的软体动物。这些有多触角的头足类动物，是对一个分布式文件系统高度并行的形象比喻。">
<meta name="keywords" content="存储,分布式,原理">
<meta property="og:type" content="article">
<meta property="og:title" content="Ceph浅析：结构、工作原理及流程">
<meta property="og:url" content="https://jelly54.github.io/ceph-principle/index.html">
<meta property="og:site_name" content="爱吃jelly的Jelly">
<meta property="og:description" content="Ceph浅析：结构、工作原理及流程 其命名和UCSC（Ceph诞生地）的吉祥物有关，这个吉祥物是“Sammy”，一个香蕉色的蛞蝓，就是头足类中无壳的软体动物。这些有多触角的头足类动物，是对一个分布式文件系统高度并行的形象比喻。">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://s2.ax1x.com/2019/10/15/K92EuR.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/10/15/K92kv9.jpg">
<meta property="og:image" content="https://s2.ax1x.com/2019/10/15/K92i34.jpg">
<meta property="og:image" content="https://s2.ax1x.com/2019/10/15/K92FgJ.png">
<meta property="og:updated_time" content="2020-03-07T03:47:15.807Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Ceph浅析：结构、工作原理及流程">
<meta name="twitter:description" content="Ceph浅析：结构、工作原理及流程 其命名和UCSC（Ceph诞生地）的吉祥物有关，这个吉祥物是“Sammy”，一个香蕉色的蛞蝓，就是头足类中无壳的软体动物。这些有多触角的头足类动物，是对一个分布式文件系统高度并行的形象比喻。">
<meta name="twitter:image" content="https://s2.ax1x.com/2019/10/15/K92EuR.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://jelly54.github.io/ceph-principle/">






  <meta name="baidu-site-verification" content="t21ZdXzSqf">



<meta name="google-site-verification" content="VmrkZiYJtPrIIH8zlY82aG2_l1Ykrp3JZ3HQbMt0qCo">

  <title>Ceph浅析：结构、工作原理及流程 | 爱吃jelly的Jelly</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>
    
    <a href="https://github.com/jelly54" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewbox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"/><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"/></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">爱吃jelly的Jelly</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">就揪啾  就是 Jelly</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>
            
            关于作者
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br>
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off" placeholder="搜索..." spellcheck="false" type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://jelly54.github.io/ceph-principle/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Big Jelly">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="爱吃jelly的Jelly">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">Ceph浅析：结构、工作原理及流程</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-09-12T20:11:45+08:00">
                2019-09-12
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/分布式/" itemprop="url" rel="index">
                    <span itemprop="name">分布式</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  8k
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  28
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h1 id="Ceph浅析：结构、工作原理及流程"><a href="#Ceph浅析：结构、工作原理及流程" class="headerlink" title="Ceph浅析：结构、工作原理及流程"></a>Ceph浅析：结构、工作原理及流程</h1><blockquote>
<p>其命名和UCSC（Ceph诞生地）的吉祥物有关，这个吉祥物是“Sammy”，一个香蕉色的蛞蝓，就是头足类中无壳的软体动物。这些有多触角的头足类动物，是对一个分布式文件系统高度并行的形象比喻。</p>
</blockquote>
<a id="more"></a>

<hr>
<h1 id="Ceph的结构"><a href="#Ceph的结构" class="headerlink" title="Ceph的结构"></a>Ceph的结构</h1><h2 id="Ceph系统的层次结构"><a href="#Ceph系统的层次结构" class="headerlink" title="Ceph系统的层次结构"></a>Ceph系统的层次结构</h2><p>Ceph存储系统的逻辑层次结构如下图所示：</p>
<p><a href="https://imgchr.com/i/K92EuR" target="_blank" rel="noopener"><img src="https://s2.ax1x.com/2019/10/15/K92EuR.png" alt="K92EuR.png"></a></p>
<p>自下向上，可以将Ceph系统分为四个层次：</p>
<h3 id="（1）基础存储系统RADOS"><a href="#（1）基础存储系统RADOS" class="headerlink" title="（1）基础存储系统RADOS"></a>（1）基础存储系统RADOS</h3><p>（Reliable, Autonomic, Distributed Object Store，即可靠的、自动化的、分布式的对象存储）</p>
<p>顾名思义，这一层本身就是一个完整的对象存储系统，所有存储在Ceph系统中的用户数据事实上最终都是由这一层来存储的。而Ceph的高可靠、高可扩展、高性能、高自动化等等特性本质上也是由这一层所提供的。因此，理解RADOS是理解Ceph的基础与关键。</p>
<p>物理上，RADOS由大量的存储设备节点组层，每个节点拥有自己的硬件资源（CPU、内存、硬盘、网络），并运行着操作系统和文件系统。4.2、4.3节将对RADOS进行展开介绍。</p>
<h3 id="（2）基础库librados"><a href="#（2）基础库librados" class="headerlink" title="（2）基础库librados"></a>（2）基础库librados</h3><p>这一层的功能是对RADOS进行抽象和封装，并向上层提供API，以便直接基于RADOS（而不是整个Ceph）进行应用开发。特别要注意的是，RADOS是一个对象存储系统，因此，librados实现的API也只是针对对象存储功能的。</p>
<p>RADOS采用C++开发，所提供的原生librados API包括C和C++两种，其文档参见[2]。物理上，librados和基于其上开发的应用位于同一台机器，因而也被称为本地API。应用调用本机上的librados API，再由后者通过socket与RADOS集群中的节点通信并完成各种操作。</p>
<h3 id="（3）高层应用接口"><a href="#（3）高层应用接口" class="headerlink" title="（3）高层应用接口"></a>（3）高层应用接口</h3><p>这一层包括了三个部分：RADOS GW（RADOS Gateway）、 RBD（Reliable Block Device）和Ceph FS（Ceph File System），其作用是在librados库的基础上提供抽象层次更高、更便于应用或客户端使用的上层接口。</p>
<p>其中，RADOS GW是一个提供与Amazon S3和Swift兼容的RESTful API的gateway，以供相应的对象存储应用开发使用。RADOS GW提供的API抽象层次更高，但功能则不如librados强大。因此，开发者应针对自己的需求选择使用。</p>
<p>RBD则提供了一个标准的块设备接口，常用于在虚拟化的场景下为虚拟机创建volume。目前，Red Hat已经将RBD驱动集成在KVM/QEMU中，以提高虚拟机访问性能。</p>
<p>Ceph FS是一个POSIX兼容的分布式文件系统。由于还处在开发状态，因而Ceph官网并不推荐将其用于生产环境中。</p>
<h3 id="（4）应用层"><a href="#（4）应用层" class="headerlink" title="（4）应用层"></a>（4）应用层</h3><p>这一层就是不同场景下对于Ceph各个应用接口的各种应用方式，例如基于librados直接开发的对象存储应用，基于RADOS GW开发的对象存储应用，基于RBD实现的云硬盘等等。</p>
<p>在上文的介绍中，有一个地方可能容易引起困惑：RADOS自身既然已经是一个对象存储系统，并且也可以提供librados API，为何还要再单独开发一个RADOS GW？</p>
<p>理解这个问题，事实上有助于理解RADOS的本质，因此有必要在此加以分析。粗看起来，librados和RADOS GW的区别在于，librados提供的是本地API，而RADOS GW提供的则是RESTful API，二者的编程模型和实际性能不同。而更进一步说，则和这两个不同抽象层次的目标应用场景差异有关。换言之，虽然RADOS和S3、Swift同属分布式对象存储系统，但RADOS提供的功能更为基础、也更为丰富。这一点可以通过对比看出。</p>
<p>由于Swift和S3支持的API功能近似，这里以S3举例说明。S3提供的API功能主要包括：</p>
<p><strong>用户管理操作：</strong> 用户认证、获取账户信息、列出容器列表等；</p>
<p><strong>容器管理操作：</strong> 创建/删除容器、读取容器信息、列出容器内对象列表等；</p>
<p><strong>对象管理操作：</strong> 对象的写入、读取、复制、更新、删除、访问许可设置、元数据读取或更新等。</p>
<p>由此可见，S3（以及Swift）提供的API所操作的“对象”只有三个：用户账户、用户存储数据对象的容器、数据对象。并且，所有的操作均不涉及存储系统 的底层硬件或系统信息。不难看出，这样的API设计完全是针对对象存储应用开发者和对象存储应用用户的，并且假定其开发者和用户关心的内容更偏重于账户和数据的管理，而对底层存储系统细节不感兴趣，更不关心效率、性能等方面的深入优化。?</p>
<p>而librados API的设计思想则与此完全不同。一方面，librados中没有账户、容器这样的高层概念；另一方面，librados API向开发者开放了大量的RADOS状态信息与配置参数，允许开发者对RADOS系统以及其中存储的对象的状态进行观察，并强有力地对系统存储策略进行控制。换言之，通过调用librados API，应用不仅能够实现对数据对象的操作，还能够实现对RADOS系统的管理和配置。这对于S3和Swift的RESTful API设计是不可想像的，也是没有必要的。</p>
<p>基于上述分析对比，不难看出，librados事实上更适合对于系统有着深刻理解，同时对于功能定制扩展和性能深度优化有着强烈需求的高级用户。基于librados的开发可能更适合于在私有Ceph系统上开发专用应用，或者为基于Ceph的公有存储系统开发后台数据管理、处理应用。而RADOS GW则更适合于常见的基于web的对象存储应用开发，例如公有云上的对象存储服务。?</p>
<h2 id="RADOS的逻辑结构"><a href="#RADOS的逻辑结构" class="headerlink" title="RADOS的逻辑结构"></a>RADOS的逻辑结构</h2><p>RADOS的系统逻辑结构如下图所示</p>
<p><a href="https://imgchr.com/i/K92kv9" target="_blank" rel="noopener"><img src="https://s2.ax1x.com/2019/10/15/K92kv9.jpg" alt="K92kv9.jpg"></a></p>
<p>在使用RADOS系统时，大量的客户端程序通过与OSD或者monitor的交互获取cluster map，然后直接在本地进行计算，得出对象的存储位置后，便直接与对应的OSD通信，完成数据的各种操作。</p>
<p>可见，在此过程中，只要保证cluster map不频繁更新，则客户端显然可以不依赖于任何元数据服务器，不进行任何查表操作，便完成数据访问流程。在RADOS的运行过程中，cluster map的更新完全取决于系统的状态变化，而导致这一变化的常见事件只有两种：<strong>OSD出现故障，或者RADOS规模扩大。</strong> 而正常应用场景下，这两种事件发生 的频率显然远远低于客户端对数据进行访问的频率。</p>
<h2 id="OSD的逻辑结构"><a href="#OSD的逻辑结构" class="headerlink" title="OSD的逻辑结构"></a>OSD的逻辑结构</h2><p>根据定义，OSD可以被抽象为两个组成部分，即系统部分和守护进程（OSD deamon）部分。</p>
<p>OSD的系统部分本质上就是一台安装了操作系统和文件系统的计算机，其硬件部分至少包括一个单核的处理器、一定数量的内存、一块硬盘以及一张网卡。</p>
<p>由于这么小规模的x86架构服务器并不实用（事实上也见不到），因而实际应用中通常将多个OSD集中部署在一台更大规模的服务器上。在选择系统配置时，应当 能够保证每个OSD占用一定的计算能力、一定量的内存和一块硬盘。同时，应当保证该服务器具备足够的网络带宽。具体的硬件配置选择可以参考。</p>
<p>在 上述系统平台上，每个OSD拥有一个自己的OSD deamon。这个deamon负责完成OSD的所有逻辑功能，包括与monitor和其他OSD（事实上是其他OSD的deamon）通信以维护更新系 统状态，与其他OSD共同完成数据的存储和维护，与client通信完成各种数据对象操作等等。</p>
<p>Ceph系统的逻辑结构就介绍到这里。下篇文章将着重说明Ceph（主要是RADOS）的工作原理和操作流程。</p>
<p>如图所示，RADOS集群主要由两种节点组成。一种是为数众多的、负责完成数据存储和维护功能的OSD（Object Storage Device），另一种则是若干个负责完成系统状态检测和维护的monitor。OSD和monitor之间相互传输节点状态信息，共同得出系统的总体工 作状态，并形成一个全局系统状态记录数据结构，即所谓的cluster map。这个数据结构与RADOS提供的特定算法相配合，便实现了Ceph“无需查表，算算就好”的核心机制以及若干优秀特性。</p>
<hr>
<h1 id="Ceph的工作原理及流程"><a href="#Ceph的工作原理及流程" class="headerlink" title="Ceph的工作原理及流程"></a>Ceph的工作原理及流程</h1><p>本节将对Ceph的工作原理和若干关键工作流程进行扼要介绍。如前所述，由于Ceph的功能实现本质上依托于RADOS，因而，此处的介绍事实上也是针对RADOS进行。对于上层的部分，特别是RADOS GW和RBD，由于现有的文档中（包括Sage的论文中）并未详细介绍，还请读者多多包涵。</p>
<p>首先介绍RADOS中最为核心的、基于计算的对象寻址机制，然后说明对象存取的工作流程，之后介绍RADOS集群维护的工作过程，最后结合Ceph的结构和原理对其技术优势加以回顾和剖析。</p>
<h2 id="寻址流程"><a href="#寻址流程" class="headerlink" title="寻址流程"></a>寻址流程</h2><p>Ceph系统中的寻址流程如下图所示：</p>
<p><a href="https://imgchr.com/i/K92i34" target="_blank" rel="noopener"><img src="https://s2.ax1x.com/2019/10/15/K92i34.jpg" alt="K92i34.jpg"></a></p>
<p>上图左侧的几个概念说明如下：</p>
<ol>
<li><p>File – 此处的file就是用户需要存储或者访问的文件。对于一个基于Ceph开发的对象存储应用而言，这个file也就对应于应用中的“对象”，也就是用户直接操作的“对象”。</p>
</li>
<li><p>Ojbect—— 此处的object是RADOS所看到的“对象”。Object与上面提到的file的区别是，object的最大size由RADOS限定（通常为2MB或4MB），以便实现底层存储的组织管理。因此，当上层应用向RADOS存入size很大的file时，需要将file切分成统一大小的一系列object（最后一个的大小可以不同）进行存储。为避免混淆，在本文中将尽量避免使用中文的“对象”这一名词，而直接使用file或object进行说明。</p>
</li>
<li><p>PG（Placement Group）—— 顾名思义，PG的用途是对object的存储进行组织和位置映射。具体而言，一个PG负责组织若干个object（可以为数千个甚至更多），但一个object只能被映射到一个PG中，即，PG和object之间是“一对多”映射关系。同时，一个PG会被映射到n个OSD上，而每个OSD上都会承载大量的PG，即，PG和OSD之间是“多对多”映射关系。在实践当中，n至少为2，如果用于生产环境，则至少为3。一个OSD上的PG则可达到数百个。事实上，PG数量的设置牵扯到数据分布的均匀性问题。关于这一点，下文还将有所展开。</p>
</li>
<li><p>OSD—— 即object storage device，前文已经详细介绍，此处不再展开。唯一需要说明的是，OSD的数量事实上也关系到系统的数据分布均匀性，因此其数量不应太少。在实践当中，至少也应该是数十上百个的量级才有助于Ceph系统的设计发挥其应有的优势。</p>
</li>
<li><p>Failure domain—— 这个概念在论文中并没有进行定义，好在对分布式存储系统有一定概念的读者应该能够了解其大意。</p>
</li>
</ol>
<p><strong>基于上述定义，便可以对寻址流程进行解释了。具体而言， Ceph中的寻址至少要经历以下三次映射。</strong></p>
<h2 id="三次映射："><a href="#三次映射：" class="headerlink" title="三次映射："></a>三次映射：</h2><h3 id="1-File-gt-object映射"><a href="#1-File-gt-object映射" class="headerlink" title="1. File -&gt; object映射"></a>1. File -&gt; object映射</h3><p>这次映射的目的是，将用户要操作的file，映射为RADOS能够处理的object。其映射十分简单，本质上就是按照object的最大size对file进行切分，相当于RAID中的条带化过程。这种切分的好处有二：一是让大小不限的file变成最大size一致、可以被RADOS高效管理的object；二是让对单一file实施的串行处理变为对多个object实施的并行化处理。</p>
<p>每一个切分后产生的object将获得唯一的oid，即object id。其产生方式也是线性映射，极其简单。图中，ino是待操作file的元数据，可以简单理解为该file的唯一id。ono则是由该file切分产生的某个object的序号。而oid就是将这个序号简单连缀在该file id之后得到的。举例而言，如果一个id为filename的file被切分成了三个object，则其object序号依次为0、1和2，而最终得到的oid就依次为filename0、filename1和filename2。</p>
<p>这里隐含的问题是，<strong>ino的唯一性必须得到保证，否则后续映射无法正确进行。</strong></p>
<h3 id="2-Object-gt-PG映射"><a href="#2-Object-gt-PG映射" class="headerlink" title="2. Object -&gt; PG映射"></a>2. Object -&gt; PG映射</h3><p>在file被映射为一个或多个object之后，就需要将每个object独立地映射到一个PG中去。这个映射过程也很简单，如图中所示，其计算公式是：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hash(oid) &amp; mask -&gt; pgid</span><br></pre></td></tr></table></figure>

<p>由此可见，其计算由两步组成。首先是使用Ceph系统指定的一个静态哈希函数计算oid的哈希值，将oid映射成为一个近似均匀分布的伪随机值。然后，将这个伪随机值和mask按位相与，得到最终的PG序号（pgid）。根据RADOS的设计，给定PG的总数为m（m应该为2的整数幂），则mask的值为m-1。因此，哈希值计算和按位与操作的整体结果事实上是从所有m个PG中近似均匀地随机选择一个。基于这一机制，当有大量object和大量PG时，RADOS能够保证object和PG之间的近似均匀映射。又因为object是由file切分而来，大部分object的size相同，因而，这一映射最终保证了，各个PG中存储的object的总数据量近似均匀。</p>
<p>从介绍不难看出，这里反复强调了 <strong>“大量”</strong>。只有当object和PG的数量较多时，这种伪随机关系的近似均匀性才能成立，Ceph的数据存储均匀性才有保证。为保证“大量”的成立，<strong>一方面，object的最大size应该被合理配置，以使得同样数量的file能够被切分成更多的object；另一方面，Ceph也推荐PG总数应该为OSD总数的数百倍，以保证有足够数量的PG可供映射。</strong></p>
<h3 id="3-PG-gt-OSD映射"><a href="#3-PG-gt-OSD映射" class="headerlink" title="3. PG -&gt; OSD映射"></a>3. PG -&gt; OSD映射</h3><p>第三次映射就是将作为object的逻辑组织单元的PG映射到数据的实际存储单元OSD。如图所示，RADOS采用一个名为 <strong>CRUSH</strong> 的算法，将pgid代入其中，然后得到一组共n个OSD。这n个OSD即共同负责存储和维护一个PG中的所有object。前已述及，n的数值可以根据实际应用中对于可靠性的需求而配置，在生产环境下通常为3。具体到每个OSD，则由其上运行的OSD deamon负责执行映射到本地的object在本地文件系统中的存储、访问、元数据维护等操作。</p>
<p>和“object -&gt; PG”映射中采用的哈希算法不同，这个 <strong>CRUSH算法的结果不是绝对不变的</strong> ，而是受到其他因素的影响。</p>
<p>其影响因素主要有二：</p>
<p>一是 当前系统状态，也就是上文逻辑结构中曾经提及的cluster map。当系统中的OSD状态、数量发生变化时，cluster map可能发生变化，而这种变化将会影响到PG与OSD之间的映射。</p>
<p>二是 存储策略配置。这里的策略主要与安全相关。利用策略配置，系统管理员可以指定承载同一个PG的3个OSD分别位于数据中心的不同服务器乃至机架上，从而进一步改善存储的可靠性。</p>
<p>因此，<strong>只有在系统状态（cluster map）和存储策略都不发生变化的时候，PG和OSD之间的映射关系才是固定不变的。</strong> 在实际使用当中，策略一经配置通常不会改变。而系统状态的改变或者是由于设备损坏，或者是因为存储集群规模扩大。好在Ceph本身提供了对于这种变化的自动化支持，因而，即便PG与OSD之间的映射关系发生了变化，也并不会对应用造成困扰。</p>
<p>事实上，Ceph正是需要有目的的利用这种动态映射关系。正是利用了CRUSH的动态特性，<strong>Ceph可以将一个PG根据需要动态迁移到不同的OSD组合上，从而自动化地实现高可靠性、数据分布re-blancing等特性。</strong></p>
<p>之所以在此次映射中使用CRUSH算法，而不是其他哈希算法，原因之一正是CRUSH具有上述可配置特性，可以根据管理员的配置参数决定OSD的物理位置映射策略；另一方面是因为CRUSH具有特殊的“稳定性”，也即，当系统中加入新的OSD，导致系统规模增大时，大部分PG与OSD之间的映射关系不会发生改变，只有少部分PG的映射关系会发生变化并引发数据迁移。这种可配置性和稳定性都不是普通哈希算法所能提供的。因此，CRUSH算法的设计也是Ceph的核心内容之一，具体介绍可以参考另一篇文章。</p>
<p>至此为止，Ceph通过三次映射，完成了从file到object、PG和OSD整个映射过程。通观整个过程，可以看到，<strong>这里没有任何的全局性查表操作需求。</strong> 至于唯一的全局性数据结构cluster map，在后文中将加以介绍。可以在这里指明的是，cluster map的维护和操作都是轻量级的，不会对系统的可扩展性、性能等因素造成不良影响。</p>
<p>一个可能出现的困惑是：为什么需要同时设计第二次和第三次映射？难道不重复么？关于这一点，Sage在其论文中解说不多，而笔者个人的分析如下：</p>
<p>我们可以反过来想像一下，如果没有PG这一层映射，又会怎么样呢？在这种情况下，一定需要采用某种算法，将object直接映射到一组OSD上。<strong>如果这种算法是某种固定映射的哈希算法，则意味着一个object将被固定映射在一组OSD上，当其中一个或多个OSD损坏时，object无法被自动迁移至其他OSD上（因为映射函数不允许），当系统为了扩容新增了OSD时，object也无法被re-balance到新的OSD上（同样因为映射函数不允许）。</strong> 这些限制都违背了Ceph系统高可靠性、高自动化的设计初衷。</p>
<p>如果采用一个动态算法（例如仍然采用CRUSH算法）来完成这一映射，似乎是可以避免静态映射导致的问题。但是，其结果将是各个OSD所处理的本地元数据量爆增，由此带来的计算复杂度和维护工作量也是难以承受的。</p>
<p>例如，在Ceph的现有机制中，一个OSD平时需要和与其共同承载同一个PG的其他OSD交换信息，以确定各自是否工作正常，是否需要进行维护操作。由于一个OSD上大约承载数百个PG，每个PG内通常有3个OSD，因此，一段时间内，一个OSD大约需要进行数百至数千次OSD信息交换。</p>
<p>然而，<strong>如果没有PG的存在，则一个OSD需要和与其共同承载同一个object的其他OSD交换信息。</strong> 由于每个OSD上承载的object很可能高达数百万个，因此，同样长度的一段时间内，一个OSD大约需要进行的OSD间 <strong>信息交换将暴涨至数百万乃至数千万次。</strong> 而这种状态维护成本显然过高。</p>
<p>综上所述，笔者认为，引入PG的好处至少有二：一方面实现了object和OSD之间的动态映射，从而为Ceph的可靠性、自动化等特性的实现留下了空间；另一方面也有效简化了数据的存储组织，大大降低了系统的维护管理开销。理解这一点，对于彻底理解Ceph的对象寻址机制，是十分重要的。</p>
<h2 id="数据操作流程"><a href="#数据操作流程" class="headerlink" title="数据操作流程"></a>数据操作流程</h2><p>此处将首先以file写入过程为例，对数据操作流程进行说明。</p>
<p>为简化说明，便于理解，此处进行若干假定。首先，假定待写入的file较小，无需切分，仅被映射为一个object。其次，假定系统中一个PG被映射到3个OSD上。</p>
<p>基于上述假定，则file写入流程可以被下图表示：</p>
<p><a href="https://imgchr.com/i/K92FgJ" target="_blank" rel="noopener"><img src="https://s2.ax1x.com/2019/10/15/K92FgJ.png" alt="K92FgJ.png"></a></p>
<p>如图所示，当某个client需要向Ceph集群写入一个file时，<strong>首先需要在本地完成前边所叙述的寻址流程，</strong> 将file变为一个object，然后 <strong>找出存储该object的一组三个OSD。</strong> 这三个OSD具有各自不同的序号，序号最靠前的那个OSD就是这一组中的Primary OSD，而后两个则依次是Secondary OSD和Tertiary OSD。</p>
<p>找出三个OSD后，<strong>client将直接和Primary OSD通信</strong>，发起写入操作（步骤1）。Primary OSD收到请求后，分别向Secondary OSD和Tertiary OSD发起写入操作（步骤2、3）。<strong>当Secondary OSD和Tertiary OSD各自完成写入操作后，将分别向Primary OSD发送确认信息（步骤4、5）。</strong> 当Primary OSD确信其他两个OSD的写入完成后，则自己也完成数据写入，并向client确认object写入操作完成（步骤6）。</p>
<p>之所以采用这样的写入流程，本质上是为了保证写入过程中的可靠性，尽可能避免造成数据丢失。同时，由于client只需要向Primary OSD发送数据，因此，在Internet使用场景下的外网带宽和整体访问延迟又得到了一定程度的优化。</p>
<p>当然，这种可靠性机制必然导致较长的延迟，特别是，如果等到所有的OSD都将数据写入磁盘后再向client发送确认信号，则整体延迟可能难以忍受。因此，<strong>Ceph可以分两次向client进行确认。</strong> 当各个OSD都将数据写入内存缓冲区后，就先向client发送一次确认，此时client即可以向下执行。待各个OSD都将数据写入磁盘后，会向client发送一个最终确认信号，此时client可以根据需要删除本地数据。</p>
<p>分析上述流程可以看出，在正常情况下，<strong>client可以独立完成OSD寻址操作，而不必依赖于其他系统模块。因此，大量的client可以同时和大量的OSD进行并行操作。同时，如果一个file被切分成多个object，这多个object也可被并行发送至多个OSD。</strong></p>
<p>从OSD的角度来看，由于同一个OSD在不同的PG中的角色不同，因此，其工作压力也可以被尽可能均匀地分担，从而避免单个OSD变成性能瓶颈。</p>
<p>如果需要读取数据，client只需完成同样的寻址过程，并直接和Primary OSD联系。目前的Ceph设计中，被读取的数据仅由Primary OSD提供。但目前也有分散读取压力以提高性能的讨论。</p>
<h2 id="集群维护"><a href="#集群维护" class="headerlink" title="集群维护"></a>集群维护</h2><p>前面的介绍中已经提到，由若干个monitor共同负责整个Ceph集群中所有OSD状态的发现与记录，并且共同形成cluster map的master版本，然后扩散至全体OSD以及client。<strong>OSD使用cluster map进行数据的维护，而client使用cluster map进行数据的寻址。</strong></p>
<p>在集群中，各个monitor的功能总体上是一样的，其相互间的关系可以被简单理解为主从备份关系。因此，在下面的讨论中不对各个monitor加以区分。</p>
<p>略显出乎意料的是，monitor并不主动轮询各个OSD的当前状态。正相反，<strong>OSD需要向monitor上报状态信息。</strong> 常见的上报有两种情况：一是新的OSD被加入集群，二是某个OSD发现自身或者其他OSD发生异常。在收到这些上报信息后，monitor将更新cluster map信息并加以扩散。其细节将在下文中加以介绍。</p>
<h2 id="Cluster-map的实际内容包括"><a href="#Cluster-map的实际内容包括" class="headerlink" title="Cluster map的实际内容包括"></a>Cluster map的实际内容包括</h2><h3 id="（1）-Epoch，即版本号"><a href="#（1）-Epoch，即版本号" class="headerlink" title="（1） Epoch，即版本号"></a>（1） Epoch，即版本号</h3><p>Cluster map的epoch是一个单调递增序列。Epoch越大，则cluster map版本越新。因此，持有不同版本cluster map的OSD或client可以简单地通过比较epoch决定应该遵从谁手中的版本。而monitor手中必定有epoch最大、版本最新的cluster map。当任意两方在通信时发现彼此epoch值不同时，将默认先将cluster map同步至高版本一方的状态，再进行后续操作。</p>
<h3 id="（2）各个OSD的网络地址"><a href="#（2）各个OSD的网络地址" class="headerlink" title="（2）各个OSD的网络地址"></a>（2）各个OSD的网络地址</h3><h3 id="（3）各个OSD的状态"><a href="#（3）各个OSD的状态" class="headerlink" title="（3）各个OSD的状态"></a>（3）各个OSD的状态</h3><p>OSD状态的描述分为两个维度：up或者down（表明OSD是否正常工作），in或者out（表明OSD是否在至少一个PG中）。因此，对于任意一个OSD，共有四种可能的状态：</p>
<ul>
<li><p>Up且in：说明该OSD正常运行，且已经承载至少一个PG的数据。这是一个OSD的标准工作状态；</p>
</li>
<li><p>Up且out：说明该OSD正常运行，但并未承载任何PG，其中也没有数据。一个新的OSD刚刚被加入Ceph集群后，便会处于这一状态。而一个出现故障的OSD被修复后，重新加入Ceph集群时，也是处于这一状态；</p>
</li>
<li><p>Down且in：说明该OSD发生异常，但仍然承载着至少一个PG，其中仍然存储着数据。这种状态下的OSD刚刚被发现存在异常，可能仍能恢复正常，也可能会彻底无法工作；</p>
</li>
<li><p>Down且out：说明该OSD已经彻底发生故障，且已经不再承载任何PG。</p>
</li>
</ul>
<h3 id="（4）CRUSH算法配置参数"><a href="#（4）CRUSH算法配置参数" class="headerlink" title="（4）CRUSH算法配置参数"></a>（4）CRUSH算法配置参数</h3><p>CRUSH算法根据种每个设备的权重尽可能概率平均地分配数据。参数有两个，一 表明了Ceph集群的物理层级关系（cluster hierarchy），二 位置映射规则（placement rules）。</p>
<h2 id="情景复现"><a href="#情景复现" class="headerlink" title="情景复现"></a>情景复现</h2><p>根据cluster map的定义可以看出，其版本变化通常只会由（3）和（4）两项信息的变化触发。而这两者相比，（3）发生变化的概率更高一些。这可以通过下面对OSD工作状态变化过程的介绍加以反映。</p>
<h3 id="新OSD上线"><a href="#新OSD上线" class="headerlink" title="新OSD上线"></a>新OSD上线</h3><p>一个新的OSD上线后，首先根据配置信息与monitor通信。Monitor将其加入cluster map，并设置为up且out状态，再将最新版本的cluster map发给这个新OSD。</p>
<p>收到monitor发来的cluster map之后，这个新OSD计算出自己所承载的PG（为简化讨论，此处我们假定这个新的OSD开始只承载一个PG），以及和自己承载同一个PG的其他OSD。然后，新OSD将与这些OSD取得联系。<strong>如果这个PG目前处于降级状态</strong> （即承载该PG的OSD个数少于正常值，如正常应该是3个，此时只有2个或1个。这种情况通常是OSD故障所致），<strong>则其他OSD将把这个PG内的所有对象和元数据复制给新OSD。</strong> 数据复制完成后，新OSD被置为up且in状态。而cluster map内容也将据此更新。这事实上是一个自动化的failure recovery过程。当然，<strong>即便没有新的OSD加入，降级的PG也将计算出其他OSD实现failure recovery。</strong></p>
<p>如果<strong>该PG目前一切正常，则这个新OSD将替换掉现有OSD中的一个</strong>（PG内将重新选出Primary OSD），<strong>并承担其数据。</strong> 在数据复制完成后，新OSD被置为up且in状态，而被替换的OSD将退出该PG（但状态通常仍然为up且in，因为还要承载其他PG）。而cluster map内容也将据此更新。这事实上是一个自动化的数据re-balancing过程。</p>
<h3 id="OSD异常"><a href="#OSD异常" class="headerlink" title="OSD异常"></a>OSD异常</h3><p>如果一个OSD发现和自己共同承载一个PG的另一个OSD无法联通，则会将这一情况 <strong>上报</strong> monitor。此外，如果一个OSD deamon发现自身工作状态异常，也将把异常情况主动 <strong>上报</strong> 报给monitor。</p>
<p>在上述情况下，monitor将把出现问题的OSD的状态设为down且in。如果超过某一预订时间期限，该OSD仍然无法恢复正常，则其状态将被设置为down且out。反之，如果该OSD能够恢复正常，则其状态会恢复为up且in。在上述这些状态变化发生之后，monitor都将更新cluster map并进行扩散。这事实上是自动化的failure detection过程。</p>
<h3 id="cluster-map-小结"><a href="#cluster-map-小结" class="headerlink" title="cluster map 小结"></a>cluster map 小结</h3><p>由之前介绍可以看出，对于一个Ceph集群而言，即便由数千个甚至更多OSD组成，cluster map的数据结构大小也并不惊人。同时，cluster map的状态更新并不会频繁发生。即便如此，Ceph依然对cluster map信息的扩散机制进行了优化，以便减轻相关计算和通信压力。</p>
<ul>
<li><p>首先，cluster map信息是以增量形式扩散的。如果任意一次通信的双方发现其epoch不一致，则版本更新的一方将把二者所拥有的cluster map的差异发送给另外一方。</p>
</li>
<li><p>其次，cluster map信息是以异步且lazy的形式扩散的。也即，monitor并不会在每一次cluster map版本更新后都将新版本广播至全体OSD，而是在有OSD向自己上报信息时，将更新回复给对方。类似的，各个OSD也是在和其他OSD通信时，将更新发送给版本低于自己的对方。</p>
</li>
</ul>
<p>基于上述机制，Ceph避免了由于cluster map版本更新而引起的广播风暴。这虽然是一种异步且lazy的机制，但根据Sage论文中的结论，<strong>对于一个由n个OSD组成的Ceph集群，任何一次版本更新能够在O(log(n))时间复杂度内扩散到集群中的任何一个OSD上。</strong></p>
<p><strong>一个可能被问到的问题是：</strong> 既然这是一种异步和lazy的扩散机制，则在版本扩散过程中，系统必定出现各个OSD看到的cluster map不一致的情况，这是否会导致问题？</p>
<p>答案是：<strong>不会。</strong> 事实上，如果一个client和它要访问的PG内部的各个OSD看到的cluster map状态一致，则访问操作就可以正确进行。而<strong>如果这个client或者PG中的某个OSD和其他几方的cluster map不一致，则根据Ceph的机制设计，这几方将首先同步cluster map至最新状态，并进行必要的数据re-balancing操作，然后即可继续正常访问。</strong></p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过上述介绍，我们可以简要了解Ceph究竟是如果基于cluster map机制，并由monitor、OSD和client共同配合完成集群状态的维护与数据访问的。特别的，基于这个机制，事实上可以自然而然的完成自动化的数据备份、数据re-balancing、故障探测和故障恢复，并不需要复杂的特殊设计。这一点确实让人印象深刻。</p>

      
    </div>
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束 <i class="fa fa-paw"></i> 感谢您的阅读-------------</div>
    
</div>

      
    </div>
    <div>
      
        
<div class="my_post_copyright">
  <script src="//cdn.bootcss.com/clipboard.js/1.5.10/clipboard.min.js"></script>
  
  <!-- JS库 sweetalert 可修改路径 -->
  <script src="https://cdn.bootcss.com/jquery/2.0.0/jquery.min.js"></script>
  <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
  <p><span>本文标题:</span><a href="/ceph-principle/">Ceph浅析：结构、工作原理及流程</a></p>
  <p><span>文章作者:</span><a href="/" title="访问 Big Jelly 的个人博客">Big Jelly</a></p>
  <p><span>发布时间:</span>2019年09月12日 - 20:11</p>
  <p><span>最后更新:</span>2020年03月07日 - 11:47</p>
  <p><span>原始链接:</span><a href="/ceph-principle/" title="Ceph浅析：结构、工作原理及流程">https://jelly54.github.io/ceph-principle/</a>
    <span class="copy-path" title="点击复制文章链接"><i class="fa fa-clipboard" data-clipboard-text="https://jelly54.github.io/ceph-principle/" aria-label="复制成功！"></i></span>
  </p>
  <p><span>许可协议:</span><i class="fa fa-creative-commons"></i> <a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank" title="Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)">署名-非商业性使用-禁止演绎 4.0 国际</a> 转载请保留原文链接及作者。</p>  
</div>
<script> 
    var clipboard = new Clipboard('.fa-clipboard');
      $(".fa-clipboard").click(function(){
      clipboard.on('success', function(){
        swal({   
          title: "",   
          text: '复制成功',
          icon: "success", 
          showConfirmButton: true
          });
        });
    });  
</script>

      
    </div>
    

    
      <div>
        <div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
    <span>打赏</span>
  </button>
  <div id="QR" style="display: none;">

    
      <div id="wechat" style="display: inline-block">
        <img id="wechat_qr" src="/images/wechatpay.png" alt="Big Jelly 微信支付">
        <p>微信支付</p>
      </div>
    

    
      <div id="alipay" style="display: inline-block">
        <img id="alipay_qr" src="/images/alipay.png" alt="Big Jelly 支付宝">
        <p>支付宝</p>
      </div>
    

    

  </div>
</div>

      </div>
    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/存储/" rel="tag"><i class="fa fa-tag"></i> 存储</a>
          
            <a href="/tags/分布式/" rel="tag"><i class="fa fa-tag"></i> 分布式</a>
          
            <a href="/tags/原理/" rel="tag"><i class="fa fa-tag"></i> 原理</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/ceph-cros/" rel="next" title="Ceph跨域问题">
                <i class="fa fa-chevron-left"></i> Ceph跨域问题
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/springs-start/" rel="prev" title="SpringSecurity简单入门">
                SpringSecurity简单入门 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
	<div id="gitalk-container"></div>

  

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- 展示广告 -->
<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3331353808689126" data-ad-slot="4663393885" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script>


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image" src="/images/avatar.jpg" alt="Big Jelly">
            
              <p class="site-author-name" itemprop="name">Big Jelly</p>
              <p class="site-description motion-element" itemprop="description">Nothing is easier than to deceive oneself</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">55</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">15</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">68</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a rel="external nofollow" href="https://github.com/jelly54" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a rel="external nofollow" href="mailto:jelly_54@163.com" target="_blank" title="E-Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>E-Mail</a>
                  </span>
                
            </div>
          

          
          

          
          
            <div class="links-of-blogroll motion-element links-of-blogroll-inline">
              <div class="links-of-blogroll-title">
                <i class="fa  fa-fw fa-link"></i>
                外链小站
              </div>
              <ul class="links-of-blogroll-list">
                
                  <li class="links-of-blogroll-item">
                    <a href="https://leetcode.com/" title="LeetCode" rel="external nofollow" target="_blank">LeetCode</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://www.dooccn.com/" title="在线编辑代码" rel="external nofollow" target="_blank">在线编辑代码</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://www.chuangzaoshi.com/code" title="创造狮导航" rel="external nofollow" target="_blank">创造狮导航</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://24mail.chacuo.net/?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq" title="24Mail" rel="external nofollow" target="_blank">24Mail</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://e.xitu.io/" title="掘金酱" rel="external nofollow" target="_blank">掘金酱</a>
                  </li>
                
              </ul>
            </div>
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#Ceph浅析：结构、工作原理及流程"><span class="nav-number">1.</span> <span class="nav-text">Ceph浅析：结构、工作原理及流程</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Ceph的结构"><span class="nav-number">2.</span> <span class="nav-text">Ceph的结构</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Ceph系统的层次结构"><span class="nav-number">2.1.</span> <span class="nav-text">Ceph系统的层次结构</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#（1）基础存储系统RADOS"><span class="nav-number">2.1.1.</span> <span class="nav-text">（1）基础存储系统RADOS</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（2）基础库librados"><span class="nav-number">2.1.2.</span> <span class="nav-text">（2）基础库librados</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（3）高层应用接口"><span class="nav-number">2.1.3.</span> <span class="nav-text">（3）高层应用接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（4）应用层"><span class="nav-number">2.1.4.</span> <span class="nav-text">（4）应用层</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#RADOS的逻辑结构"><span class="nav-number">2.2.</span> <span class="nav-text">RADOS的逻辑结构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#OSD的逻辑结构"><span class="nav-number">2.3.</span> <span class="nav-text">OSD的逻辑结构</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Ceph的工作原理及流程"><span class="nav-number">3.</span> <span class="nav-text">Ceph的工作原理及流程</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#寻址流程"><span class="nav-number">3.1.</span> <span class="nav-text">寻址流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#三次映射："><span class="nav-number">3.2.</span> <span class="nav-text">三次映射：</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-File-gt-object映射"><span class="nav-number">3.2.1.</span> <span class="nav-text">1. File -&gt; object映射</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-Object-gt-PG映射"><span class="nav-number">3.2.2.</span> <span class="nav-text">2. Object -&gt; PG映射</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-PG-gt-OSD映射"><span class="nav-number">3.2.3.</span> <span class="nav-text">3. PG -&gt; OSD映射</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#数据操作流程"><span class="nav-number">3.3.</span> <span class="nav-text">数据操作流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#集群维护"><span class="nav-number">3.4.</span> <span class="nav-text">集群维护</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Cluster-map的实际内容包括"><span class="nav-number">3.5.</span> <span class="nav-text">Cluster map的实际内容包括</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#（1）-Epoch，即版本号"><span class="nav-number">3.5.1.</span> <span class="nav-text">（1） Epoch，即版本号</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（2）各个OSD的网络地址"><span class="nav-number">3.5.2.</span> <span class="nav-text">（2）各个OSD的网络地址</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（3）各个OSD的状态"><span class="nav-number">3.5.3.</span> <span class="nav-text">（3）各个OSD的状态</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#（4）CRUSH算法配置参数"><span class="nav-number">3.5.4.</span> <span class="nav-text">（4）CRUSH算法配置参数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#情景复现"><span class="nav-number">3.6.</span> <span class="nav-text">情景复现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#新OSD上线"><span class="nav-number">3.6.1.</span> <span class="nav-text">新OSD上线</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#OSD异常"><span class="nav-number">3.6.2.</span> <span class="nav-text">OSD异常</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#cluster-map-小结"><span class="nav-number">3.6.3.</span> <span class="nav-text">cluster map 小结</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#小结"><span class="nav-number">3.7.</span> <span class="nav-text">小结</span></a></li></ol></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

      <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
      <!-- 侧边栏广告 -->
      <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3331353808689126" data-ad-slot="7563237003" data-ad-format="auto" data-full-width-responsive="true"></ins>
      <script>
      	(adsbygoogle = window.adsbygoogle || []).push({});
      </script>
    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright"> &copy; 2017 &mdash; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Big Jelly</span>

</div>

<div class="powered-by">
	
		<span class="post-meta-item-icon">
			<i class="fa fa-area-chart"></i>
		</span>
		
			<span class="post-meta-item-text">Site words total count&#58;</span>
		
		<span title="Site words total count">140.8k</span>
	
  
	
		<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

		<span class="post-meta-divider">|</span>
		<span class="post-visit-item-icon">
			<i class="fa fa-user-md"></i>
		</span>
		<span class="busuanzi_container_site_uv">
			Visitors to this site: <span id="busuanzi_value_site_uv"></span>
		</span>
	
  


	
</div>
        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  


  











  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  

  
  
    <script type="text/javascript" src="/lib/canvas-nest/canvas-nest.min.js"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
  <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
  <script src="/js/md5.min.js"></script>
   <script type="text/javascript">
        var gitalk = new Gitalk({
          clientID: 'ca31692554c3aea431f0',
          clientSecret: '1173c9e9c3c9038304c9a5b08929a359cae9f847',
          repo: 'jelly54.github.io',
          owner: 'jelly54',
          admin: ['jelly54'],
          id: md5(location.pathname),
          distractionFreeMode: 'true'
        })
        gitalk.render('gitalk-container')
       </script>

  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  



  
<script type="text/javascript" color="0,0,255" opacity="0.7" zindex="-2" count="99" src="//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js"></script>


<!-- 页面点击小红心 -->
<script type="text/javascript" src="/js/src/love.js"></script>
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":true,"model":{"scale":1,"jsonPath":"/live2dw/assets/miku.model.json"},"display":{"position":"right","width":200,"height":400,"hOffset":0,"vOffset":-5},"mobile":{"show":true,"scale":0.5},"react":{"opacity":0.9},"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body>
</html>
